﻿#region Header

/*
Behavioral Rating of Dancing Human Crowds based on Motion Patterns
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using paravili.Services;
using Sebarf.Services.Interfaces;

#endregion

namespace paravili.Steps {
	/// <summary>
	/// Finds clusters within a list of <see cref="ColoredPoint"/>
	/// Uses the DENCLUE method for doing that,
	/// see
	///  http://www.ivh.uni-hannover.de/optiv/Methoden/ClustMet/ClustMet.pdf
	/// or
	///  \000_Papers\Clustering\Data_Mgmt11_Clustering.pdf
	/// </summary>
	public class FindClustersByDENCLUEMethod :
		ProcessStepConverterWithMeasurement<ColoredPoint[,], IList<IList<ColoredPoint>>> {
		#region Public Properties

		[ConfigurableNumericValue(Name = "minimal clustersize :=> min xi of the denclue algo", RangeFrom = 1, RangeTo = 5000)
		]
		public int Min_Xi_ClusterSize { get; set; }

		[ConfigurableNumericValue(Name = "max clustersize :=> max xi of the denclue algo", RangeFrom = 1, RangeTo = 5000)]
		public int Max_Xi_ClusterSize { get; set; }

		[ConfigurableNumericValue(Name = "cluster searching sensivity :=> sigma of the denclue algo", RangeFrom = 1,
			RangeTo = 50)]
		public int Sigma { get; set; }

		[ConfigurabelBooleanValue(Name = "VisualizeClusters")]
		public bool VisualizeClusters { get; set; }

		[ConfigurabelStringValue(Name = "VisualizeClustersInImage")]
		public String VisualizeClustersInImage { get; set; }

		[ConfigurabelBooleanValue(Name = "Draw only valid clusters")]
		public bool DrawOnlyValidClusters { get; set; }

		[ConfigurabelBooleanValue(Name = "Draw the number of the cluster")]
		public bool DrawClusterNumber { get; set; }

		[ServiceRequest(IsOptional = true)]
		public IImageLocatorService ImageLocatorService { get; set; }

		#endregion

		#region Public Methods

		public FindClustersByDENCLUEMethod() {
			Min_Xi_ClusterSize = 4;
			Max_Xi_ClusterSize = 2000;
			Sigma = 2;
			VisualizeClusters = true;
			DrawOnlyValidClusters = true;
			VisualizeClustersInImage = "BgrToLabConverter.ImageData";
			DrawClusterNumber = true;
		}

		#endregion

		#region Private Methods

		private void findNeigbours(ColoredPoint p, int row, int col, ref ColoredPoint[,] candidates,
									ICollection<ColoredPoint> cluster) {
			int clusterSearchingSensivity = Sigma;
			var pointsToProcess = new List<KeyValuePair<Point, ColoredPoint>> { (new KeyValuePair<Point, ColoredPoint>(new Point(col, row), p)) };

			candidates[col, row] = null;

			int cnt0 = 0;
			do {
				KeyValuePair<Point, ColoredPoint> o = pointsToProcess.First();

				int minRows = Math.Max(0, o.Key.Y - clusterSearchingSensivity);
				int maxRows = Math.Min(candidates.GetLength(1) - 1, o.Key.Y + clusterSearchingSensivity);
				int minCols = Math.Max(0, o.Key.X - clusterSearchingSensivity);
				int maxCols = Math.Min(candidates.GetLength(0) - 1, o.Key.X + clusterSearchingSensivity);

				bool found = false;
				for (int i = minCols; i <= maxCols; i++) {
					for (int y = minRows; y <= maxRows; y++) {
						if (y == o.Key.X && i == o.Key.Y) {
							continue;
						}
						cnt0++;

						ColoredPoint newPoint = candidates[i, y];
						if (newPoint != null) {
							found = true;
							candidates[i, y] = null;
							cluster.Add(newPoint);
						}
					}
				}

				pointsToProcess.Remove(o);
				if (found) {
					pointsToProcess.Add(new KeyValuePair<Point, ColoredPoint>(new Point(maxCols, maxRows),
																				candidates[maxCols, maxRows]));
					pointsToProcess.Add(new KeyValuePair<Point, ColoredPoint>(new Point(minCols, minRows),
																				candidates[maxCols, maxRows]));
					pointsToProcess.Add(new KeyValuePair<Point, ColoredPoint>(new Point(minCols, maxRows),
																				candidates[maxCols, maxRows]));
					pointsToProcess.Add(new KeyValuePair<Point, ColoredPoint>(new Point(maxCols, minRows),
																				candidates[maxCols, maxRows]));
				}
			} while (pointsToProcess.Count > 0);
			return;
		}

		protected override IList<IList<ColoredPoint>> Convert(ColoredPoint[,] toProcess) {
			if (toProcess == null) {
				return null;
			}
			IList<IList<ColoredPoint>> foundClusters = new List<IList<ColoredPoint>>();
			bool visualize = (VisualizeClusters && ImageLocatorService != null);
			Image<Lab, Byte> image = null;
			if (visualize) {
				image = ImageLocatorService.GetImage(VisualizeClustersInImage);
			}
			int index = 1;
			var font = new MCvFont(FONT.CV_FONT_HERSHEY_PLAIN, 1, 1);
			int cols = toProcess.GetLength(0);
			int rows = toProcess.GetLength(1);
			for (int row = 0; row < rows; row++) {
				for (int col = 0; col < cols; col++) {
					ColoredPoint candidatePoint = toProcess[col, row];
					if (candidatePoint != null) {
						var clusterOfPoint = new List<ColoredPoint>();
						findNeigbours(candidatePoint, row, col, ref toProcess, clusterOfPoint);
						if (clusterOfPoint.Count == 0) {
							continue;
						}
						if (!DrawOnlyValidClusters) {
							if (image != null) {
								image.DrawPolyline(clusterOfPoint.Select(g => g.P).ToArray(), true, new Lab(1000, 1000, 1000), 1);
							}
						}

						if (Min_Xi_ClusterSize < clusterOfPoint.Count && clusterOfPoint.Count < Max_Xi_ClusterSize) {
							foundClusters.Add(clusterOfPoint);
							if (DrawOnlyValidClusters) {
								if (image != null) {
									image.DrawPolyline(clusterOfPoint.Select(g => g.P).ToArray(), true, new Lab(1000, 1000, 1000), 1);
									if (DrawClusterNumber) {
										image.Draw(index.ToString(), ref font, candidatePoint.P, new Lab(16, 16, 16));
									}
								}
							}
						}
						index++;
					}
				}
			}

			StatisticValuesProviderService.SetStatisticValue("Clusters.Found", foundClusters.Count);
			return foundClusters;
		}

		#endregion
	}
}